<?php
/**
 * 基本的请求处理类
 * 
 * 提供从$_POST和$_GET中取数据的能力,同时可以进行XSS过滤.
 * 另外cookie也可以通过本类的实例来获取和设置.
 * 
 * @author Leo Ning <leo.ning@like18.com>
 * @version 1.0
 * @since 1.0
 * @copyright 2008-2011 LIKE18 INC.
 * @package core 
 */
include 'XssCleaner.php';
class Request {
	private $use_xss_clean = false;
	private $xss_cleaner;
	private $cookie_options;
	private $quotes_gpc;
	private $clientinfo = array ();
	private $ipdatafile;
	public function __construct($xss_clean = false) {
		$this->use_xss_clean = $xss_clean;
		$this->quotes_gpc = get_magic_quotes_gpc ();
		$this->xss_cleaner = new XssCleaner ();
		if (defined ( "COOKIE_DOMAIN" )) {
			$this->cookie_options ['cookie_domain'] = COOKIE_DOMAIN;
		}
		if (defined ( "COOKIE_PATH" )) {
			$this->cookie_options ['cookie_path'] = COOKIE_PATH;
		}
		if (defined ( "COOKIE_SECURE" )) {
			$this->cookie_options ['cookie_secure'] = COOKIE_SECURE;
		}
		if (defined ( "COOKIE_EXPIRE" )) {
			$this->cookie_options ['cookie_expire'] = COOKIE_EXPIRE;
		}
		$this->cookie_options = array_merge ( array ('cookie_domain' => '', 'cookie_path' => '/', 'cookie_secure' => false, 'cookie_expire' => 0 ), $this->cookie_options );
		$this->_sanitize_globals ();
		$this->ipdatafile = apply_filter ( "get_ip_data_file", "" );
	}
	/**
	 * 获取客户端传过来的值无论是通过GET方式还是POST方式
	 * @param string $name 
	 * @param mixed $default
	 * @param boolean $xss_clean 是否进行xss过滤
	 * @return mixed
	 */
	public function get($name, $default = '', $xss_clean = false) {
		$ary = isset ( $_POST [$name] ) ? $_POST : $_GET;
		if (! isset ( $ary [$name] )) {
			return $default;
		}
		if ($xss_clean === true && $this->use_xss_clean === false) {
			return $this->xss_cleaner->xss_clean ( $ary [$name] );
		}
		return $ary [$name];
	}
	/**
	 * 
	 * 读取$names指定的变量
	 * @param array $names 要读取的变量
	 * @param boolean $xss_clean 是否进行xss过滤
	 * @return array 
	 */
	public function gets($names, $xss_clean = false) {
		$values = array ();
		foreach ( $names as $key => $dft ) {
			if (is_numeric ( $key )) {
				$values [$dft] = $this->get ( $dft, '', $xss_clean );
			} else {
				$values [$key] = $this->get ( $key, $dft, $xss_clean );
			}
		}
		return $values;
	}
	/**
	 * 是否传递这个值而不关心值是什么
	 * 
	 * @param string $name
	 * @return boolean
	 */
	public function has($name) {
		$ary = isset ( $_POST [$name] ) ? $_POST : $_GET;
		return isset ( $ary [$name] );
	}
	/**
	 * 从COOKIE读取数据
	 * @param string $name
	 * @param mixed $default
	 * @param boolean $xss_clean 是否进行xss过滤
	 * @return mixed
	 */
	public function cookie($name, $default = '', $xss_clean = false) {
		$ary = $_COOKIE;
		if (! isset ( $ary [$name] )) {
			return $default;
		}
		if ($xss_clean === true && $this->use_xss_clean === false) {
			return $this->xss_cleaner->xss_clean ( $ary [$name] );
		}
		return $ary [$name];
	}
	/**
	 * 
	 * 保留本次请求数据
	 */
	public function save($name) {
		sess_add ( '__nutzp_savedRequest_' . $name, $_POST );
	}
	/**
	 * 
	 * 恢复上一次保留的数据
	 */
	public function restore($name, $del) {
		if ($del) {
			$savedPost = sess_del ( '__nutzp_savedRequest_' . $name, array () );
		} else {
			$savedPost = sess_get ( '__nutzp_savedRequest_' . $name, array () );
		}
		if (! empty ( $savedPost )) {
			$_POST = array_merge_recursive ( $_POST, $savedPost );
		}
	}
	public function delete($name) {
		sess_del ( '__nutzp_savedRequest_' . $name, array () );
	}
	/**
	 * 设置COOKIE,当$value=null或$expire小于0时从COOKIE中删除$name.
	 * @param string $name
	 * @param mixed $value
	 * @param int $expire
	 */
	public function setCookie($name, $value = null, $expire = null,$domain = '') {
		extract ( $this->cookie_options );
		$expire = is_numeric ( $expire ) ? $expire : $cookie_expire;
		$expire = is_null ( $value ) ? time () - 36000 : (empty ( $expire ) ? 0 : time () + $expire);
		if(!empty($domain)){
			$cookie_domain = $domain;
		}
		@setcookie ( $name, $value, $expire, $cookie_path, $cookie_domain, $cookie_secure );
	}
	/**
	 * 请求客户端信息
	 * @return array key=>value(ip,city,idc,port,referer)
	 */
	public function clientinfo() {
		if (empty ( $this->clientinfo )) {
			if (isset ( $_SERVER ["HTTP_X_FORWARDED_FOR"] )) {
				$ip = $_SERVER ["HTTP_X_FORWARDED_FOR"];
			} elseif (isset ( $_SERVER ["HTTP_CLIENT_IP"] )) {
				$ip = $_SERVER ["HTTP_CLIENT_IP"];
			} else {
				$ip = $_SERVER ["REMOTE_ADDR"];
			}
			$this->clientinfo ['url'] = $_SERVER ['REQUEST_URI'] . '[' . $_SERVER ['QUERY_STRING'] . ']';
			$this->clientinfo ['ip'] = $ip;
			$this->clientinfo ['port'] = $_SERVER ['REMOTE_PORT'];
			$this->clientinfo ['referer'] = $_SERVER ['HTTP_REFERER'];
			if (($ipaddr = $this->convertip ( $ip )) === false) {
				$this->clientinfo ['county'] = '';
				$this->clientinfo ['city'] = '';
				$this->clientinfo ['idc'] = '';
			} else {
				$ipaddr = explode ( ' ', iconv ( 'GB2312', 'UTF-8', $ipaddr ) );
				$this->clientinfo ['city'] = array_shift ( $ipaddr );
				$this->clientinfo ['idc'] = implode ( " ", $ipaddr );
			}
		}
		return $this->clientinfo;
	}
	//处理全局输入
	private function _sanitize_globals() {
		$_GET = $this->_clean_input_data ( $_GET );
		$_POST = $this->_clean_input_data ( $_POST );
		unset ( $_COOKIE ['$Version'] );
		unset ( $_COOKIE ['$Path'] );
		unset ( $_COOKIE ['$Domain'] );
		$_COOKIE = $this->_clean_input_data ( $_COOKIE );
	}
	/**
	 * Clean Input Data
	 *
	 * This is a helper function. It escapes data and
	 * standardizes newline characters to \n
	 *
	 * @access	private
	 * @param	string
	 * @return	string
	 */
	private function _clean_input_data($str) {
		if (is_array ( $str )) {
			$new_array = array ();
			foreach ( $str as $key => $val ) {
				$new_array [$this->_clean_input_keys ( $key )] = $this->_clean_input_data ( $val );
			}
			return $new_array;
		}
		
		// We strip slashes if magic quotes is on to keep things consistent
		if ($this->quotes_gpc) {
			$str = stripslashes ( $str );
		}
		
		// Should we filter the input data?
		if ($this->use_xss_clean === true) {
			$str = $this->xss_cleaner->xss_clean ( $str );
		}
		
		// Standardize newlines
		if (strpos ( $str, "\r" ) !== FALSE) {
			$str = str_replace ( array ("\r\n", "\r" ), "\n", $str );
		}
		
		return $str;
	}
	/**
	 * Clean Keys
	 *
	 * This is a helper function. To prevent malicious users
	 * from trying to exploit keys we make sure that keys are
	 * only named with alpha-numeric text and a few other items.
	 *
	 * @access	private
	 * @param	string
	 * @return	string
	 */
	private function _clean_input_keys($str) {
		if (! preg_match ( '/^[a-z0-9:_\/-]+$/i', $str )) {
			exit ( 'Disallowed Key Characters.' );
		}
		return $str;
	}
	
	/**
	 * 
	 * @param string $ip
	 */
	private function convertip($ip) {
		//IP数据文件路径
		$dat_path = $this->ipdatafile;
		if (! is_readable ( $dat_path )) {
			return false;
		}
		//检查IP地址
		if (! preg_match ( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $ip )) {
			return false;
		}
		//打开IP数据文件
		if (! $fd = @fopen ( $dat_path, 'rb' )) {
			return false;
		}
		//分解IP进行运算，得出整形数
		$ip = explode ( '.', $ip );
		$ipNum = $ip [0] * 16777216 + $ip [1] * 65536 + $ip [2] * 256 + $ip [3];
		//获取IP数据索引开始和结束位置
		$DataBegin = fread ( $fd, 4 );
		$DataEnd = fread ( $fd, 4 );
		$ipbegin = implode ( '', unpack ( 'L', $DataBegin ) );
		if ($ipbegin < 0)
			$ipbegin += pow ( 2, 32 );
		$ipend = implode ( '', unpack ( 'L', $DataEnd ) );
		if ($ipend < 0)
			$ipend += pow ( 2, 32 );
		$ipAllNum = ($ipend - $ipbegin) / 7 + 1;
		$BeginNum = 0;
		$EndNum = $ipAllNum;
		//使用二分查找法从索引记录中搜索匹配的IP记录
		while ( $ip1num > $ipNum || $ip2num < $ipNum ) {
			$Middle = intval ( ($EndNum + $BeginNum) / 2 );
			//偏移指针到索引位置读取4个字节
			fseek ( $fd, $ipbegin + 7 * $Middle );
			$ipData1 = fread ( $fd, 4 );
			if (strlen ( $ipData1 ) < 4) {
				fclose ( $fd );
				return false;
			}
			//提取出来的数据转换成长整形，如果数据是负数则加上2的32次幂
			$ip1num = implode ( '', unpack ( 'L', $ipData1 ) );
			if ($ip1num < 0)
				$ip1num += pow ( 2, 32 );
			
		//提取的长整型数大于我们IP地址则修改结束位置进行下一次循环
			if ($ip1num > $ipNum) {
				$EndNum = $Middle;
				continue;
			}
			//取完上一个索引后取下一个索引
			$DataSeek = fread ( $fd, 3 );
			if (strlen ( $DataSeek ) < 3) {
				fclose ( $fd );
				return false;
			}
			$DataSeek = implode ( '', unpack ( 'L', $DataSeek . chr ( 0 ) ) );
			fseek ( $fd, $DataSeek );
			$ipData2 = fread ( $fd, 4 );
			if (strlen ( $ipData2 ) < 4) {
				fclose ( $fd );
				return false;
			}
			$ip2num = implode ( '', unpack ( 'L', $ipData2 ) );
			if ($ip2num < 0)
				$ip2num += pow ( 2, 32 );
			if ($ip2num < $ipNum) {
				if ($Middle == $BeginNum) {
					fclose ( $fd );
					return false;
				}
				$BeginNum = $Middle;
			}
		}
		$ipFlag = fread ( $fd, 1 );
		if ($ipFlag == chr ( 1 )) {
			$ipSeek = fread ( $fd, 3 );
			if (strlen ( $ipSeek ) < 3) {
				fclose ( $fd );
				return false;
			}
			$ipSeek = implode ( '', unpack ( 'L', $ipSeek . chr ( 0 ) ) );
			fseek ( $fd, $ipSeek );
			$ipFlag = fread ( $fd, 1 );
		}
		if ($ipFlag == chr ( 2 )) {
			$AddrSeek = fread ( $fd, 3 );
			if (strlen ( $AddrSeek ) < 3) {
				fclose ( $fd );
				return false;
			}
			$ipFlag = fread ( $fd, 1 );
			if ($ipFlag == chr ( 2 )) {
				$AddrSeek2 = fread ( $fd, 3 );
				if (strlen ( $AddrSeek2 ) < 3) {
					fclose ( $fd );
					return false;
				}
				$AddrSeek2 = implode ( '', unpack ( 'L', $AddrSeek2 . chr ( 0 ) ) );
				fseek ( $fd, $AddrSeek2 );
			} else {
				fseek ( $fd, - 1, SEEK_CUR );
			}
			while ( ($char = fread ( $fd, 1 )) != chr ( 0 ) ) {
				$ipAddr2 .= $char;
			}
			$AddrSeek = implode ( '', unpack ( 'L', $AddrSeek . chr ( 0 ) ) );
			fseek ( $fd, $AddrSeek );
			while ( ($char = fread ( $fd, 1 )) != chr ( 0 ) ) {
				$ipAddr1 .= $char;
			}
		} else {
			fseek ( $fd, - 1, SEEK_CUR );
			while ( ($char = fread ( $fd, 1 )) != chr ( 0 ) )
				$ipAddr1 .= $char;
			
			$ipFlag = fread ( $fd, 1 );
			if ($ipFlag == chr ( 2 )) {
				$AddrSeek2 = fread ( $fd, 3 );
				if (strlen ( $AddrSeek2 ) < 3) {
					fclose ( $fd );
					return false;
				}
				$AddrSeek2 = implode ( '', unpack ( 'L', $AddrSeek2 . chr ( 0 ) ) );
				fseek ( $fd, $AddrSeek2 );
			} else {
				fseek ( $fd, - 1, SEEK_CUR );
			}
			//读取地址信息
			while ( ($char = fread ( $fd, 1 )) != chr ( 0 ) ) {
				$ipAddr2 .= $char;
			}
		}
		@fclose ( $fd );
		//最后做相应的替换操作后返回结果
		if (preg_match ( '/http/i', $ipAddr2 )) {
			$ipAddr2 = '';
		}
		$ipaddr = "$ipAddr1 $ipAddr2";
		$ipaddr = preg_replace ( '/CZ88.Net/is', '', $ipaddr );
		$ipaddr = preg_replace ( '/^s*/is', '', $ipaddr );
		$ipaddr = preg_replace ( '/s*$/is', '', $ipaddr );
		if (preg_match ( '/http/i', $ipaddr ) || $ipaddr == '') {
			$ipaddr = false;
		}
		return $ipaddr;
	}
}